package cn.ecnu.search.service.impl;

import cn.ecnu.search.model.PaperModel;
import cn.ecnu.search.parameter.Bucket;
import cn.ecnu.search.parameter.SearchParameter;
import cn.ecnu.search.parameter.SearchResult;
import cn.ecnu.search.service.SearchService;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ElasticsearchOperations elasticsearchOperations;

    @Override
    public SearchResult search(SearchParameter parameter) {
        NativeSearchQuery query = queryBasedOnParam(parameter);
        SearchHits<PaperModel> paper = elasticsearchOperations.search(query, PaperModel.class, IndexCoordinates.of("paper"));
        return covertToResult(paper);
    }

    NativeSearchQuery queryBasedOnParam(SearchParameter parameter) {
        List<QueryBuilder> filters = new ArrayList<>();
        if (!StringUtils.isEmpty(parameter.getField())) {
           filters.add(QueryBuilders.termQuery("fields.fieldName.keyword", parameter.getField()));
        }

        if (!StringUtils.isEmpty(parameter.getAuthor())) {
            filters.add(QueryBuilders.termQuery("authors.authorName.keyword", parameter.getAuthor()));
        }

        if (!StringUtils.isEmpty(parameter.getKind())) {
            filters.add(QueryBuilders.termQuery("type.keyword", parameter.getKind()));
        }

        if (!StringUtils.isEmpty(parameter.getJournal())) {
            filters.add(QueryBuilders.termQuery("journal.keyword", parameter.getJournal()));
        }

        if (parameter.getYear() != null) {
            filters.add(QueryBuilders.rangeQuery("publishTime").from(LocalDate.ofYearDay(parameter.getYear(), 1), true).to(LocalDate.ofYearDay(parameter.getYear() + 1, 1), false));
        }

        if (!StringUtils.isEmpty(parameter.getPoster())) {
            filters.add(QueryBuilders.termQuery("posterName.keyword", parameter.getPoster()));
        }

        MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("abstracts", parameter.getKeyword());
        if (StringUtils.equals("title", parameter.getType()))
            matchQuery = QueryBuilders.matchQuery("title", parameter.getKeyword());
        if (StringUtils.equals("author", parameter.getType()))
            matchQuery = QueryBuilders.matchQuery("authors.authorName", parameter.getKeyword());
        if (StringUtils.equals("poster", parameter.getType()))
            matchQuery = QueryBuilders.matchQuery("posterName", parameter.getKeyword());


        FieldSortBuilder sort = SortBuilders.fieldSort("publishTime").order(SortOrder.DESC);
        if (StringUtils.equals("post_time", parameter.getSort()))
            sort = SortBuilders.fieldSort("postTime").order(SortOrder.DESC);
        if (StringUtils.equals("note_num", parameter.getSort()))
            sort = SortBuilders.fieldSort("noteNum").order(SortOrder.DESC);

        Integer page = parameter.getPage();
        if (parameter.getPage() == null) page = 0;
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(matchQuery); // 进行must查询
        filters.forEach(boolQueryBuilder::filter); // 添加对应的filters
        return new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withSort(sort)
                .withPageable(PageRequest.of(page, 10))
                .addAggregation(AggregationBuilders.terms("fields").field("fields.fieldName.keyword"))
                .addAggregation(AggregationBuilders.terms("authors").field("authors.authorName.keyword"))
                .addAggregation(AggregationBuilders.terms("kinds").field("type.keyword"))
                .addAggregation(AggregationBuilders.terms("journals").field("journal.keyword"))
                .addAggregation(AggregationBuilders.dateHistogram("years").calendarInterval(DateHistogramInterval.YEAR).format("YYYY").field("publishTime"))
                .addAggregation(AggregationBuilders.terms("posters").field("posterName.keyword"))
                .build();

    }



    SearchResult covertToResult(SearchHits<PaperModel> hits) {
        SearchResult result = new SearchResult();
        List<PaperModel> papers = hits.get().map(SearchHit::getContent).collect(Collectors.toList());
        result.setPapers(papers);
        Map<String, List<Bucket>> aggregations = new HashMap<>();
        if (hits.getAggregations() != null) {
            hits.getAggregations().getAsMap().forEach((key, value) -> {
                if (value instanceof ParsedStringTerms) {
                    List<Bucket> buckets = ((ParsedStringTerms) value).getBuckets().stream().map((b) -> {
                        Bucket bucket = new Bucket();
                        bucket.setKey(b.getKey().toString());
                        bucket.setDocCount(b.getDocCount());
                        return bucket;
                    }).collect(Collectors.toList());
                    aggregations.put(key, buckets);
                }
                else if (value instanceof ParsedDateHistogram) {
                    List<Bucket> buckets = ((ParsedDateHistogram) value).getBuckets().stream().map((b) -> {
                        Bucket bucket = new Bucket();
                        bucket.setKey(b.getKeyAsString());
                        bucket.setDocCount(b.getDocCount());
                        return bucket;
                    }).collect(Collectors.toList());
                    aggregations.put(key, buckets);
                }
            });
        }
        result.setAggregations(aggregations);
        return result;
    }
}
